package com.nebula.core.dsl.operation;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.SQLOrderingSpecification;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.statement.SQLSelectGroupByClause;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem;
import com.nebula.core.client.NebulaSession;
import com.nebula.core.constant.Constant;
import com.nebula.core.dsl.WhereStatement;
import com.nebula.core.dsl.builder.GoStatementBuilder;
import com.vesoft.nebula.client.graph.data.ResultSet;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.nebula.core.constant.Constant.QUOTED_LITERAL_FORMAT;


/**
 * GO查询操作类
 * GO [[<M> TO] <N> {STEP|STEPS} ] FROM <vertex_list>
 * OVER <edge_type_list> [{REVERSELY | BIDIRECT}]
 * [ WHERE <conditions> ]
 * YIELD [DISTINCT] <return_list>
 * [{ SAMPLE <sample_list> | <limit_by_list_clause> }]
 * [| GROUP BY {<col_name> | expression> | <position>} YIELD <col_name>]
 * [| ORDER BY <expression> [{ASC | DESC}]]
 * [| LIMIT [<offset>,] <number_rows>];
 *
 * @author zhangtao
 */
public class GoOperation implements GoStatementBuilder.GoStatement {


    private static final Logger logger = LoggerFactory.getLogger(GoOperation.class);

    private Integer start;
    private Integer end;
    private List<Object> vidList = new ArrayList<>();
    private String reference;
    private List<String> edgeTypeList = new ArrayList<>();
    private String direction = "";
    private WhereStatement where;
    private SQLOrderBy orderBy;
    private SQLSelectGroupByClause groupBy;
    private SQLLimit limit;
    private List<SQLSelectItem> selectList = new ArrayList<>();
    private List<SQLSelectItem> groupEndSelectList = new ArrayList<>();
    private NebulaSession nebulaSession;

    private GoOperation() {
    }

    private GoOperation(NebulaSession nebulaSession) {
        this.nebulaSession = nebulaSession;
    }

    protected static GoStatementBuilder.GoStart create() {
        return new GoOperation();
    }

    protected static GoStatementBuilder.GoStart create(NebulaSession nebulaSession) {
        return new GoOperation(nebulaSession);
    }

    @Override
    public GoStatementBuilder.GoStepOut steps(int end) {
        this.end = end;
        return this;
    }

    @Override
    public GoStatementBuilder.GoStepOut steps(int start, int end) {
        this.start = start;
        this.end = end;
        return this;
    }

    @Override
    public GoStatementBuilder.GoFromOut from(Object... vids) {
        this.vidList.addAll(Arrays.stream(vids).collect(Collectors.toList()));
        return this;
    }

    @Override
    public GoStatementBuilder.GoFromOut fromPipe(String reference) {
        this.reference = reference;
        return this;
    }

    @Override
    public GoStatementBuilder.GoOverOut over(boolean in, boolean out, String... edgeType) {
        if (in && out) {
            this.direction = Constant.BIDIRECT;
        } else if (in) {
            this.direction = Constant.REVERSELY;
        }
        this.edgeTypeList.addAll(Arrays.stream(edgeType).collect(Collectors.toList()));
        return this;
    }

    @Override
    public GoStatementBuilder.GoWhereOut and(String key, String operator, Object value) {
        if (this.where == null) {
            this.where = WhereStatement.create();
        }
        this.where.and(key, operator, value);
        return this;
    }

    @Override
    public GoStatementBuilder.GoWhereOut or(String key, String operator, Object value) {
        if (this.where == null) {
            this.where = WhereStatement.create();
        }
        where.or(key, operator, value);
        return this;
    }

    @Override
    public GoStatementBuilder.GoYieldOut yield(String field, String alias) {
        this.selectList.add(new SQLSelectItem(new SQLIdentifierExpr(field), alias));
        return this;
    }

    @Override
    public GoStatementBuilder.GoYieldOut yield(String field) {
        this.selectList.add(new SQLSelectItem(new SQLIdentifierExpr(field)));
        return this;
    }

    @Override
    public GoStatementBuilder.GoOrderOut orderBy(String field) {
        this.orderBy(field, "ASC");
        return this;
    }

    @Override
    public GoStatementBuilder.GoOrderOut orderBy(String field, String order) {
        if (this.orderBy == null) {
            this.orderBy = new SQLOrderBy();
        }
        SQLSelectOrderByItem item = new SQLSelectOrderByItem();
        item.setExpr(new SQLIdentifierExpr(field));
        if (StringUtils.isNoneBlank(order)) {
            item.setType(SQLOrderingSpecification.valueOf(order.toUpperCase()));
        }
        this.orderBy.addItem(item);
        return this;
    }

    @Override
    public GoStatementBuilder.GoLimitOut limit(int pageNo, int pageSize) {
        this.limit = new SQLLimit();
        this.limit.setOffset(pageSize * (pageNo - 1));
        this.limit.setRowCount(pageSize);
        return this;
    }

    @Override
    public String build() {
        StringBuilder builder = new StringBuilder("GO ");
        //拼接steps
        if (start != null && end != null) {
            builder.append(start);
            builder.append(" TO ");
            builder.append(end);
            builder.append(" STEPS ");
        } else if (end != null) {
            builder.append(end);
            builder.append(" STEPS ");
        }

        //拼接vid
        if (!CollectionUtils.isEmpty(vidList)) {
            builder.append(vidList.stream().map(vid -> {
                if (vid instanceof Integer) {
                    return String.valueOf(vid);
                } else {
                    return String.format(QUOTED_LITERAL_FORMAT, vid);
                }
            }).collect(Collectors.joining(",", "FROM ", " ")));
        }

        //拼接引用变量
        if (StringUtils.isNotEmpty(this.reference)) {
            builder.append(" FROM ");
            builder.append(reference);
        }

        //拼接边类型
        if (CollectionUtils.isEmpty(edgeTypeList)) {
            builder.append(" OVER * ");
        } else {
            builder.append(edgeTypeList.stream().collect(Collectors.joining(",", " OVER ", " ")));
        }
        //拼接边方向
        builder.append(direction);

        //拼接where条件
        if (where != null) {
            builder.append(" WHERE ");
            builder.append(where);
        }

        //拼接返回字段
        if (!CollectionUtils.isEmpty(selectList)) {
            builder.append(" YIELD ");
            for (int i = 0; i < selectList.size(); i++) {
                if (i != 0) {
                    builder.append(",");
                }
                selectList.get(i).output(builder);
            }
        }

        //拼接排序
        if (orderBy != null) {
            builder.append(" | ");
            builder.append(SQLUtils.toMySqlString(orderBy));
        }

        //拼接分组
        if (groupBy != null) {
            builder.append(" | ");
            builder.append(SQLUtils.toMySqlString(groupBy));
        }

        //拼接分页
        if (limit != null) {
            builder.append(" | ");
            builder.append(SQLUtils.toMySqlString(limit));
        }

        //拼接返回字段
        if (!CollectionUtils.isEmpty(groupEndSelectList)) {
            builder.append(" YIELD ");
            for (int i = 0; i < groupEndSelectList.size(); i++) {
                if (i != 0) {
                    builder.append(",");
                }
                groupEndSelectList.get(i).output(builder);
            }
        }

        logger.info("查询sql:{}", builder);
        return builder.toString();
    }

    @Override
    public ResultSet excute() {
        return nebulaSession.query(this.build());
    }

    @Override
    public GoStatementBuilder.GoGroupOut groupBy(String... fields) {
        if (this.groupBy == null) {
            this.groupBy = new SQLSelectGroupByClause();
        }
        for (String field : fields) {
            SQLIdentifierExpr item = new SQLIdentifierExpr(field);
            this.groupBy.addItem(item);
        }
        return this;
    }

    @Override
    public GoStatementBuilder.GoGroupYieldOut groupYield(String field, String alias) {
        this.groupEndSelectList.add(new SQLSelectItem(new SQLIdentifierExpr(field), alias));
        return this;
    }

    @Override
    public GoStatementBuilder.GoGroupYieldOut groupYield(String field) {
        this.groupEndSelectList.add(new SQLSelectItem(new SQLIdentifierExpr(field)));
        return this;
    }

}
